//
// Created by bjliuyong on 2021/6/7.
//

#include "../../include/memory/memory_chunk.h"
#include "../../include/gc/gc_factory.h"

MemoryChunk::~MemoryChunk() {
    INFO_PRINT("[~MemoryChunk %s]\n", __FILE__);
    if (NULL != this->m_addr) {
        free(this->m_addr);
        this->m_addr = NULL;
    }
    free_available_table();
    free_used_table();
}

MemoryChunk::MemoryChunk(uint size, char *filename, uint line) : m_size(size), m_filename(filename), m_line(line) {
    this->m_addr = (pvoid) calloc(size, 1);
    if (NULL == this->m_addr) {
        ERROR_PRINT("calloc error\n");
        exit(1);
    }
    this->m_used_cell_num = 0;
    this->m_align_size = ALIGN_SIZE;
    this->m_size = m_cell_num * m_align_size;
    this->m_cell_num = ((size - 1) / m_align_size) + 1;

    if(DEFAULT_GC_TYPE == MARK_COPY){
        this->m_cell_start = 0;
        this->m_available_table.push_front(new MemoryCell(0, m_cell_num/2));
        this->m_idle_table.push_front(new MemoryCell(m_cell_num/2,m_cell_num/2));
    } else {
        this->m_available_table.push_front(new MemoryCell(0, m_cell_num));
    }
}

MemoryCell *MemoryChunk::malloc_after_gc(MemoryCell *transferCell) {
    list<MemoryCell *>::iterator iter;

    pvoid to;
    pvoid from = transferCell->ptr();
    for (iter = m_available_table.begin(); iter != m_available_table.end(); iter++) {
        MemoryCell *cell = *iter;
        if (cell->get_size() >= transferCell->get_size()) {

            transferCell->set_start(cell->get_start());

            uint cell_num = transferCell->get_size();
            cell->inc_start(cell_num)->desc_size(cell_num);

            transferCell->set_end(cell->get_start());
            transferCell->set_mark(false);

            inc_used_cell_num(cell_num);
            break;
        }
    }
    to = transferCell->ptr();

    memcpy(to, from, transferCell->get_size() * this->m_align_size);
    return transferCell;
}

MemoryCell *MemoryChunk::malloc(uint size) {
    MemoryCell *ret = real_malloc(size);

    if (NULL == ret) {
        GCFactory::getInstance()->minor_run(DEFAULT_GC_TYPE, this);
        ret = real_malloc(size);
    }

    if (NULL == ret) {
        ERROR_PRINT("OOM ...");
        exit(1);
    }

    return ret;
}

MemoryCell *MemoryChunk::real_malloc(uint size) {
    MemoryCell *ret = NULL;
    if (0 == size) {
        ERROR_PRINT("申请的内存大小不得等于0\n");
        exit(1);
    }

    uint cell_num = ((size - 1) / m_align_size) + 1;

    if (cell_num > m_cell_num) {
        ERROR_PRINT("申请的内存大小【%d】超过了最大内存【%d】\n", size, m_size);
        exit(1);
    }

    list<MemoryCell *>::iterator iter;

    for (iter = m_available_table.begin(); iter != m_available_table.end(); iter++) {
        MemoryCell *memoryCell = *iter;
        if (memoryCell->get_size() >= cell_num) {
            ret = _malloc(memoryCell, cell_num);
            break;
        }
    }
    return ret;
}

MemoryCell *MemoryChunk::_malloc(MemoryCell *cell, uint cell_num) {

    PRINT("Malloc usable cell start=%d, size=%d\n", cell->get_start(), cell->get_size());

    MemoryCell *used_cell = new MemoryCell(cell->get_start(), cell_num);
    used_cell->set_chunk(this);

    m_used_table.push_front(used_cell);

    cell->inc_start(cell_num)->desc_size(cell_num);
    PRINT("Malloced usable cell start=%d, size=%d\n", cell->get_start(), cell->get_size());

    inc_used_cell_num(cell_num);

    if (m_used_cell_num == m_cell_num) {
        free_available_table();
    } else if (cell->get_size() == 0) {
        //get_available_table().p_
    }

    return used_cell;
}

MemoryChunk *MemoryChunk::inc_used_cell_num(uint step) {
    m_used_cell_num += step;

    if (m_used_cell_num > m_cell_num) {
        ERROR_PRINT("cell of chunk size overflow\n");

        exit(1);
    }

    return this;
}

MemoryChunk *MemoryChunk::desc_used_cell_num(uint step) {
    m_used_cell_num -= step;
    return this;
}

void MemoryChunk::set_used_cell_num(uint n) {
    this->m_used_cell_num = n;
}

void MemoryChunk::free_available_table() {
    PRINT("Free available_table \n");

    list<MemoryCell *>::iterator tmp;
    for (tmp = m_available_table.begin(); tmp != m_available_table.end(); tmp++) {
        delete (*tmp);
    }

    m_available_table.clear();
}

void MemoryChunk::free_used_table() {
    PRINT("Free used_table \n");

    list<MemoryCell *>::iterator tmp;
    for (tmp = m_used_table.begin(); tmp != m_used_table.end(); tmp++) {
        delete (*tmp);
    }
    m_used_table.clear();
}

list<MemoryCell *> *MemoryChunk::get_used_table() {
    return &m_used_table;
}

list<MemoryCell *> *MemoryChunk::get_available_table() {
    return &this->m_available_table;
}

void MemoryChunk::set_available_table(list<MemoryCell *> &table) {
    this->m_available_table = table;
}

list<MemoryCell *> *MemoryChunk::get_transfer_table() {
    return &this->m_transfer_table;
}

list<MemoryCell *> * MemoryChunk::get_idle_table() {
    return &this->m_idle_table;
}

void MemoryChunk::print_available_table() {
    PRINT("[打印available_table]开始, size=%d\n", get_available_table()->size());

    list<MemoryCell *>::iterator tmp;

    for (tmp = m_available_table.begin(); tmp != m_available_table.end(); tmp++) {
        PRINT("\t cell_start=%d, cell_end=%d, cell_size=%d\n", (*tmp)->get_start(), (*tmp)->get_end(),
              (*tmp)->get_size());
    }

    PRINT("[打印available_table]结束\n");
}

void MemoryChunk::print_used_table() {
    PRINT("[打印used_table]开始, size=%d\n", get_used_table()->size());

    list<MemoryCell *>::iterator tmp;

    for (tmp = m_used_table.begin(); tmp != m_used_table.end(); tmp++) {
        PRINT("\t cell_start=%d, cell_end=%d, cell_size=%d\n", (*tmp)->get_start(), (*tmp)->get_end(),
              (*tmp)->get_size());
    }

    PRINT("[打印used_table]结束\n");
}

void MemoryChunk::print_all_table() {
    print_available_table();

    print_used_table();
}

pvoid MemoryChunk::get_addr() {
    return this->m_addr;
}

uint MemoryChunk::get_align_size() {
    return this->m_align_size;
}

uint MemoryChunk::get_cell_start() {
    return this->m_cell_start;
}

uint MemoryChunk::get_new_cell_start() {
    return this->m_cell_start == 0 ? m_cell_num / 2 : 0;
}
uint MemoryChunk::renew_cell_start() {
    this->m_cell_start = get_new_cell_start();
}

uint MemoryChunk::get_cell_num() {
    return this->m_cell_num;
}
